App Store Server Library

RSS for tag

App Store Server Library is the library for the App Store Server API and App Store Server Notifications. It provides an API client, a JWS signed data verifier, a utility to extract a transaction id from a receipt, and a promotional offer signature.

Posts under App Store Server Library tag

23 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Cannot Receive `EXPIRED` Notifications from App Store Server Notifications V2
Hello, I am currently implementing server-side handling for in-app subscription payments and using App Store Server Notifications V2 to receive notifications with a TestFlight account. However, I am not receiving EXPIRED notifications, although I am successfully receiving other notifications such as SUBSCRIBED, DID_RENEW, and DID_CHANGE_RENEWAL_PREF. Here are some details of my observations: Until a certain point, I was receiving EXPIRED notifications without any issues, but they stopped coming in after that point. Each subscription renews every 3 minutes in TestFlight account, and I receive DID_RENEW notifications 7 to 12 times. According to the official documentation, the subscriptions should renew up to 12 times, but I am not receiving exactly 12 DID_RENEW notifications. This inconsistency might be related to the issue. Other notifications (SUBSCRIBED, DID_RENEW, DID_CHANGE_RENEWAL_PREF) are received without any issues. Could anyone provide insight into why this might be happening and suggest any alternative methods to handle subscription expirations in case the EXPIRED notifications are not reliable? Thank you for your assistance. Relevant Official Documentation App Store Server Notifications V2 App Store Server Notifications V2_notificationType Testing in-app purchases with sandbox Current Server Implementation Below is the Kotlin Spring Boot server code currently implemented for handling App Store Server Notifications V2: @RestController class ProductIosController( private val productIosService: ProductIosService, private val appStoreNotificationService: AppStoreNotificationService, ) : BaseController { @PostMapping("/api/v1/ios-products/app-store-notifications-v2") fun handleNotification(@RequestBody @Valid notification: AppStoreNotificationRequest): CustomResponse { appStoreNotificationService.processNotification(notification) return CustomResponse.ok() } } @Service class AppStoreNotificationService( @Qualifier("appStoreClient") private val appStoreServerAPIClient: AppStoreServerAPIClient, @Qualifier("signedVerifier") private val signedDataVerifier: SignedDataVerifier, ) { @Transactional fun processNotification(notification: AppStoreNotificationRequest) { logger.info("signedPayload: ${notification.signedPayload}") val decodedPayload = verifyAndDecodeSignedPayload(notification.signedPayload) val notificationType = decodedPayload.notificationType val signedTransactionInfo = decodedPayload.data.signedTransactionInfo val transaction = signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo) val (user, product) = fetchUserAndProduct(transaction) when (notificationType) { SUBSCRIBED -> processSubscriptionPurchase(user, product, decodedPayload, transaction) DID_CHANGE_RENEWAL_PREF -> processSubscriptionGradeChange(user, product, decodedPayload, transaction) DID_CHANGE_RENEWAL_STATUS -> processRenewalStatusChange(transaction) OFFER_REDEEMED -> processOfferRedeemed(transaction) DID_RENEW -> processSubscriptionRenewal(user, product, decodedPayload, transaction) EXPIRED -> processSubscriptionExpiration(user, product) DID_FAIL_TO_RENEW -> processFailedRenewal(transaction) GRACE_PERIOD_EXPIRED -> processSubscriptionGracePeriodExpiration(transaction) PRICE_INCREASE -> processPriceIncrease(transaction) REFUND -> processSubscriptionRefund(transaction) REFUND_DECLINED -> processRefundDeclined(transaction) CONSUMPTION_REQUEST -> processConsumptionRequest(transaction) RENEWAL_EXTENDED -> processRenewalExtension(transaction) REVOKE -> processSubscriptionRevocation(transaction) TEST -> processTestNotification(transaction) RENEWAL_EXTENSION -> processRenewalExtension(transaction) REFUND_REVERSED -> processRefundReversed(transaction) EXTERNAL_PURCHASE_TOKEN -> processExternalPurchaseToken(transaction) else -> logger.warn("Unsupported notification type: ${notificationType.value}") } }
1
0
109
2d
App Store Server Notifications V2 always retries five times
Hi, Our app that implemented in-app payment has been reviewed and passed and is currently in operation. However, there is a problem with the App Store server notification V2. According to the url https://developer.apple.com/documentation/appstoreservernotifications/responding_to_app_store_server_notifications, it is written as follows. "When you set up the endpoints on your server to receive notifications, configure your server to send a response. Use HTTP status codes to indicate whether the App Store server notification post succeeded: Send HTTP 200, or any HTTP code between 200 and 206, if the post was successful. Send HTTP 50x or 40x to have the App Store retry the notification, if the post didn't succeed. The system considers all other HTTP codes an unsuccessful post. Your server isn’t required to return a data value. If the App Store server doesn’t receive a success response from your server after the initial notification attempt, it retries as follows: For version 2 notifications, it retries five times, at 1, 12, 24, 48, and 72 hours after the previous attempt." We are sending an HTTP status code to the Apple server by 200 or 40x or 50x when we receive an Apple notification from the server as per the document. Nevertheless, Apple Server continues to send us 5 times App Store server notifications for each transaction. I would appreciate it if you could share how we can do it. Also, we can provide the implementation code of our server through code-level support. Thank you for your support.
3
0
130
3d
My getTransactionInfo call resulted in apiError: 4040010, errorMessage: 'Transaction id not found.'
Hi, I am testing a consumable in-app purchase on my app, with a Sandbox account on an iPad device. The transaction was successful, as I saw "You're all set. Your purchase was successful. [Environment: Sandbox]. I set a break point in Xcode after the line await transaction.finish() in following code private func handle(transactionVerification result: VerificationResult ) async { switch result { case let .verified(transaction): guard let product = self.products.first(where: { $0.id == transaction.productID }) else { return } self.addPurchased(product) await transaction.finish() return. <----- breakpoint And I saw those property values for the transaction id UInt64 88*****848 originalID UInt64 437****2496 . Then I use the originalID value 437*****2496 in a server library call in node.js .... const environment = Environment.SANDBOX .... const client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment) .... const response = await client.getTransactionInfo("4379072496") I got apiError: 4040010, errorMessage: 'Transaction id not found.' Could someone please tell me if I use the library call correctly with the right id? And why I got the error? Thank you very much! Kind regards, Shih-Chin Yang [Edited by Moderator]
4
0
190
3d
How to verify receipt with AppStoreServerAPI
Hello everyone. I'm try to get out of verifyReceipt endpoint by following this link https://developer.apple.com/videos/play/wwdc2023/10143/ at the end of video so after using ReceiptUtility extract receipt data to get transationId then using getTransactionHistory to do what? it doesn't clear in the video. Where I can get document on how to verify receipt with AppStoreServerAPI.
2
0
347
Apr ’24
getTransactionHistory missing transactionId with productype Consumable
Hello everyone. I'm try to get out of verifyReceipt for my app. I'm try to following this link https://developer.apple.com/videos/play/wwdc2023/10143/ Then I'm testing by using old receipt from my app. Using receiptUtility.extractTransactionIdFromAppReceipt(appReceipt); I got this transaction Id 160000542059454. Then follow the step in the video to get all transaction history I realize that it missing transaction which have productype as consumable. If I'm using getTransactionInfo an pass that Id I can fetch the info of that transaction. So my question is how to get all transaction Id history.
1
0
318
Apr ’24
verifyReceipt The underlying connection was closed: An unexpected error occurred on a send
Hi, Starting this weekend our backend fails to connect to the production Apple verifyReceipt endpoint. It's a C# .NET service running on windows server. The full exception received is: Exception: "The underlying connection was closed: An unexpected error occurred on a send". Inner Exception: "Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host". Is someone else facing a similar issue? sending a request to the Apple production verifyReceipt URL via Postman (located on the same server) succeeds. Thanks in advance...
3
0
408
Mar ’24
App store server API request to get a user's Auto-renewel-sub and non-consumable purchases in one request.
I have an app in which there are two different subscription groups and One non-consumable product. User can have two(2) subscriptions and one non-consumable purchase [total three purchases] at a time. All three have different "originalTransactionIdentifier". How can i get user's purchase History in ONE App store server API request?
2
0
520
Jan ’24
consumable inapp purchase server side validation
Since verifyReceipt is marked as deprecated, there is appstore server library; e.g. this is the java version: https://github.com/apple/app-store-server-library-java/tree/main According to https://developer.apple.com/videos/play/wwdc2023/10143/, client (game) sends receipt to server for validation and server extracts transactionId with ReceiptUtility and asks for transaction history to appstore to receive signed transactions. During this request, server can filter to receive only consumables; e.g: TransactionHistoryRequest request = new TransactionHistoryRequest() .sort(TransactionHistoryRequest.Order.DESCENDING) .revoked(false) .productTypes(List.of( TransactionHistoryRequest.ProductType.CONSUMABLE)); Working in sanbox env, everything seems fine, I can make a successful in app purchase; but I receive below from transaction history request: APIException{httpStatusCode=404, apiError=4040010, apiErrorMessage='Transaction id not found.'} But when I use getTransactionInfo with the same extracted transactionId, I receive a successful response like below: TransactionInfoResponse{signedTransactionInfo='eyJ.... I have 2 questions here: 1- why am i getting error in TransactionHistoryRequest 2- what is the correct way to validate an in app purchase server side without verifyReceipt? Thanks in advance PS: Here are sample codes for TransactionHistoryRequest and getTransactionInfo: TransactionHistoryRequest String encodedKey = Files.readString(filePath); Environment environment = Environment.SANDBOX; AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); String receipt = "MIIUW......"; ReceiptUtility receiptUtil = new ReceiptUtility(); String transactionId = receiptUtil.extractTransactionIdFromAppReceipt(receipt); System.out.println("extracted txId is " + transactionId); if (transactionId != null) { TransactionHistoryRequest request = new TransactionHistoryRequest() .sort(TransactionHistoryRequest.Order.DESCENDING) .revoked(false) .productTypes(List.of(TransactionHistoryRequest.ProductType.CONSUMABLE)); HistoryResponse response = null; List<String> transactions = new LinkedList<>(); do { String revision = response != null ? response.getRevision() : null; System.out.println("revision is " + revision); response = client.getTransactionHistory(transactionId, revision, request); transactions.addAll(response.getSignedTransactions()); } while (response.getHasMore()); System.out.println(transactions); } getTransactionInfo String encodedKey = Files.readString(filePath); System.out.println("encoded key is " + encodedKey); String receipt = "MIIUW..."; ReceiptUtility receiptUtil = new ReceiptUtility(); String txId = receiptUtil.extractTransactionIdFromAppReceipt(receipt); Environment environment = Environment.SANDBOX; AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); try { TransactionInfoResponse response = client.getTransactionInfo(txId); String signed = response.getSignedTransactionInfo(); System.out.println(">>>" + signed.split("\\.").length); String jws_payload = signed.split("\\.")[1]; String payload = new String(Base64.decodeBase64(jws_payload)); System.out.println(response); System.out.println("payload is " + payload); } catch (APIException | IOException e) { e.printStackTrace(); }
0
0
520
Jan ’24
App Store Server + App Store Notification V2 + Apple Review
Hello With V1 notifications, all you had to do was check status 21007 in production to switch to sandbox, but with V2 how do you do that? At best you get a 401 if you query production with a sandbox transactionID. How can I ensure that transaction validation via AppStore Server uses the correct environment during the Apple review? What is the procedure for V2 notifications and Apple review? If my code deployed in production is on the product environment, what happens at review time? I don't think anyone on the web has asked this question... Any help would be appreciated Nicolas
1
0
624
Jan ’24
App Store Server Library production test notifications fail.
We're able to successfully perform test notifications using the app-store-server-library-python in SANDBOX environment, but the second we switch to PRODUCTION (for testing purposes), the call fails with 401 and it doesn't seem to reach our server at all. This suggests that something is wrong with singing production environment headers, however I've seen posts from others that suggest this is not specific to the App Store server library code. It's very clear that the libraries are marked Beta – however, most replies to questions about the v2 API are replied to with suggestions to use the library. Just FWIW – there's some contradictory advice there. Anyway, the main point is that we're currently blocked on making sure that our v2 API hooks are working properly, since we can't send production test notifications. Any idea why the signed requests would work to send sandbox test notifications, but not production environment? We've triple checked the URLs, etc – as far as we know, the private key should be the same regardless of environment. Thanks! (P.S. If anyone has been able to send v2 test notifications with the PRODUCTION environment, please let us know!)
1
0
763
Dec ’23
Is the Apple root certificate in the App Store Server API response always 'Apple Root CA - G3'?
Is the Apple root certificate in the App Store Server API response always 'Apple Root CA - G3'? When isn't it? What criteria should I set for the 'performRevocationChecking' parameter value of the verifyChain method of the ChainVerifier class in the App Store Server Library? I am implementing the 'App Store Server API' call myself. Do you include the root certificate in the certificate chain verification process? Can root certificates be forged?
1
0
875
Dec ’23
Overflow Issue in the Amount Length of GetTransactionInfo for Retrieving Transaction Details
1.We are making a request to Apple's GetTransactionInfo api: request_uri: GET https://api.storekit.itunes.apple.com/inApps/v1/transactions/570xxxxxx152928 request_response_body` :{"signedTransactionInfo":"eyJh....."} 2.Parse the signedTransactionInfo content returned by the GetTransactionInfo Api JWSTransactionDecodedPayload: { "appAccountToken": "FBACxxxx376", "bundleId": "com.test.xx", "currency": "VND", "environment": "Production", "inAppOwnershipType": "PURCHASED", "originalPurchaseDate": 1700369755000, "originalTransactionId": "57xxxxxx2928", "price": -1795967296, "productId": "com.text.xx9", "purchaseDate": 1700364444000, "quantity": 1, "signedDate": 1700364444000, "storefront": "VNM", "storefrontId": "123456", "transactionId": "57xxxxxx2928", "transactionReason": "PURCHASE", "type": "Consumable" } 3. The Apple Api returned an abnormal price: -1,795,967,296. Personal speculation: The original price of the current order item [com.text.xx9] is 2,499,000 VND, and the Apple Api multiplies the price amount by 1000, resulting in a final amount of 2,499,000,000. However, due to an overflow issue in the length of the price amount, the price in the order details becomes -1,795,967,296, a negative number. Appstore api doc: GetTransactionInfo Api docGet Transaction Info | Apple Developer Documentation JWSTransactionDecodedPayload doc:JWSTransactionDecodedPayload | Apple Developer Documentation
0
1
457
Nov ’23
Inquiry Regarding the Production-Readiness of the app-store-server Library
Dear Apple Developer Forum Community, I hope this message finds you well. We are in the process of planning a migration from the existing "verify receipt" method to the new App Store Server API using the app-store-server library in Node.js. While we are excited about the capabilities and advantages that the app-store-server library offers, we have noted that it is currently in a beta phase. As we strive to ensure a stable and reliable production environment for our application, we would like to inquire about the expected timeline for the app-store-server library to transition from beta to a production-ready release. Our decision to adopt this library is contingent upon its readiness for production use, and we are keen to align our migration plans with its development roadmap. Any insights or updates regarding the library's readiness for production use would be greatly appreciated. Thank you for your time and assistance. We look forward to your response and are eager to continue our journey of providing exceptional experiences to our users through the Apple ecosystem. Sincerely,
1
0
495
Oct ’23
jwt decode Signature verification failed
Receipt verification on my app's server suddenly started giving an error, and all billing-related processes became errors. The error that is occurring is Signature verification failed is.(FireBaseJWT throw Exception) The code is below, but it was working fine until 3 days ago. JWT::decode($lastTransaction['signedTransactionInfo'], $appleCertificate, ['ES256']); The certificate used here was created by the server developer, so I don't know how to create it. Could you please help me which certificate should I use? I tried using the ApplePKI certificate, but the same error occurred. 私のアプリのサーバでのレシート検証が突然エラーを吐くようになり、課金関連の処理が全てエラーになってしまいました。 発生しているエラーはSignature verification failedです。(FireBaseJWTのException) コードは以下になっていますが、3日前までは正常に動作していたコードです。 JWT::decode($lastTransaction['signedTransactionInfo'], $appleCertificate, ['ES256']); ここで利用している証明書はサーバ開発者に作成して貰っていたので作成方法が分かりません。 どの証明書を使えば良いか助けて貰えないでしょうか。 ApplePKIの証明書は粗方試してみましたが同様のエラーになっています。
2
0
928
Sep ’23